﻿using System;
using System.Collections.Generic;
using KLadderProposal.Interfaces;
using KLadderProposal.Interfaces.BasicStructures;

namespace KLadderProposal.Structures
{
    public class Graph : IGraph
    {
        #region Fields (3)

        private IGraphDocument _graphDocument;
        private Dictionary<string, GraphNode> _nodeSet = new Dictionary<string, GraphNode>();
        private List<GraphNode> _sortedNodes;

        #endregion Fields

        #region Constructors (1)

        public Graph(IGraphDocument document)
        {
            _graphDocument = document;
        }

        #endregion Constructors

        #region Properties (3)

        public int Count
        {
            get { return _nodeSet.Count; }
        }

        public IEnumerable<GraphNode> Nodes
        {
            get
            {
                return _nodeSet.Values;
            }
        }

        public IEnumerable<GraphNode> SortedNodes
        {
            get { return _sortedNodes; }
        }

        #endregion Properties

        #region Methods (10)

        // Public Methods (10) 

        public GraphNode AddNode(string label)
        {
            GraphNode graphNode = new GraphNode { Label = label, SequenceNumber = _nodeSet.Values.Count + 1 };
            _nodeSet.Add(graphNode.Label, graphNode);


            return graphNode;
        }

        public void AddUndirectedEdge(string fromNodeLabel, string toNodeLabel)
        {
            GraphNode from;
            GraphNode to;

            if (!_nodeSet.TryGetValue(fromNodeLabel, out from))
                from = AddNode(fromNodeLabel);

            if (!_nodeSet.TryGetValue(toNodeLabel, out to))
                to = AddNode(toNodeLabel);

            from.Neighbors[to.Label] = to;
            to.Neighbors[from.Label] = from;
        }

        public bool Contains(string label)
        {
            return FindByValue(label) != null;
        }

        public void CreateGraphToTextFile(string path, int numberOfNodes, int cliqueNumber)
        {
            char[] alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
            int addded = (int)(float)numberOfNodes / cliqueNumber;
            using (System.IO.StreamWriter file = new System.IO.StreamWriter(path, false))
            {
                for (int i = 0; i < numberOfNodes; i++)
                {
                    //for (int j = 0; j < numberOfNodes; j++)
                    //{
                    //    file.WriteLine(alpha[i] + "\t" + alpha[j] + Environment.NewLine);
                    //    file.WriteLine(alpha[j] + "\t" + alpha[i] + Environment.NewLine);
                    //}
                    if (i % addded == 0)
                    {
                        for (int j = 0; j < cliqueNumber; j++)
                        {
                            if (i != j)
                            {
                                file.WriteLine(alpha[i] + "\t" + alpha[j] + Environment.NewLine);
                                file.WriteLine(alpha[j] + "\t" + alpha[i] + Environment.NewLine);
                            }
                        }
                    }
                }


            }
        }

        public GraphNode FindByValue(string label)
        {
            GraphNode graphNode;

            if (_nodeSet.TryGetValue(label, out graphNode))
                return graphNode;

            return null;
        }

        public void LoadFrom(string filePath)
        {
            _graphDocument.Load(this, filePath);
        }

        public bool Remove(string label)
        {
            GraphNode nodeToRemove = FindByValue(label);
            if (nodeToRemove == null)
                return false;

            _nodeSet.Remove(nodeToRemove.Label);

            // enumerate through each node in the nodeSet, removing edges to this node
            foreach (GraphNode gnode in _nodeSet.Values)
            {
                int index = gnode.Neighbors.IndexOfKey(nodeToRemove.Label);
                if (index != -1)
                {
                    gnode.Neighbors.RemoveAt(index);
                }
            }

            return true;
        }

        public void SaveTo(string filePath)
        {
            _graphDocument.Save(this, filePath);
        }

        public void SaveToTextFile(string path)
        {
            using (System.IO.StreamWriter file = new System.IO.StreamWriter(path, false))
            {
                foreach (GraphNode node in Nodes)
                {
                    foreach (KeyValuePair<string, GraphNode> graphNode in node.Neighbors)
                    {
                        file.WriteLine(node.Label + "\t" + graphNode.Key + Environment.NewLine);
                    }

                }
            }
        }

        public void SortGraphByNeighbors()
        {
            _sortedNodes = new List<GraphNode>(Nodes);
            _sortedNodes.Sort((p2, p1) => p1.Neighbors.Count.CompareTo(p2.Neighbors.Count));
        }

        #endregion Methods
    }
}
